#ifndef __BUILD_PACKET_H__
#define __BUILD_PACKET_H__

#include <netinet/ip.h>

static inline uint16_t csum_fold(uint32_t sum)
{
    asm("  addl %1,%0\n" "  adcl $0xffff,%0":"=r"(sum)
            :    "r"((uint32_t) sum << 16), "0"((uint32_t) sum & 0xffff0000));
    return (uint16_t) (~(uint32_t) sum >> 16);
}

/*
 *      This is a version of ip_compute_csum() optimized for IP headers,
 *      which always checksum on 4 octet boundaries.
 *
 *      By Jorge Cwik <jorge@laser.satlink.net>, adapted for linux by
 *      Arnt Gulbrandsen.
 */

/**
 * ip_fast_csum - Compute the IPv4 header checksum efficiently.
 * iph: ipv4 header
 * ihl: length of header / 4
 */
static inline uint16_t ip_fast_csum(const void *iph, unsigned int ihl)
{
    unsigned int sum;

    asm("  movl (%1), %0\n"
            "  subl $4, %2\n"
            "  jbe 2f\n" "  addl 4(%1), %0\n" "  adcl 8(%1), %0\n" "  adcl 12(%1), %0\n" "1: adcl 16(%1), %0\n" "  lea 4(%1), %1\n" "  decl %2\n" "  jne      1b\n"
            "  adcl $0, %0\n" "  movl %0, %2\n" "  shrl $16, %0\n" "  addw %w2, %w0\n" "  adcl $0, %0\n" "  notl %0\n" "2:"
            /* Since the input registers which are loaded with iph and ihl
               are modified, we must also specify them as outputs, or gcc
               will assume they contain their original values. */
            :    "=r"(sum), "=r"(iph), "=r"(ihl)
            :    "1"(iph), "2"(ihl)
            :    "memory");
    return (uint16_t) sum;
}

/**
 * csum_tcpup_nofold - Compute an IPv4 pseudo header checksum.
 * @saddr: source address
 * @daddr: destination address
 * @len: length of packet
 * @proto: ip protocol of packet
 * @sum: initial sum to be added in (32bit unfolded)
 *
 * Returns the pseudo header checksum the input data. Result is
 * 32bit unfolded.
 */
static inline uint32_t csum_tcpudp_nofold(uint32_t saddr, uint32_t daddr, unsigned short len, unsigned short proto, uint32_t sum)
{
    asm("  addl %1, %0\n" "  adcl %2, %0\n" "  adcl %3, %0\n" "  adcl $0, %0\n":"=r"(sum)
            :    "g"(daddr), "g"(saddr), "g"((len + proto) << 8), "0"(sum));
    return sum;
}

/**
 * csum_tcpup_magic - Compute an IPv4 pseudo header checksum.
 * @saddr: source address
 * @daddr: destination address
 * @len: length of packet
 * @proto: ip protocol of packet
 * @sum: initial sum to be added in (32bit unfolded)
 *
 * Returns the 16bit pseudo header checksum the input data already
 * complemented and ready to be filled in.
 */
static inline uint16_t csum_tcpudp_magic(uint32_t saddr, uint32_t daddr, unsigned short len, unsigned short proto, uint32_t sum)
{
    return csum_fold(csum_tcpudp_nofold(saddr, daddr, len, proto, sum));
}

static inline unsigned short from32to16(unsigned a)
{
    unsigned short b = a >> 16;
    asm("addw %w2,%w0\n\t" "adcw $0,%w0\n":"=r"(b)
            :    "0"(b), "r"(a));
    return b;
}

static inline unsigned add32_with_carry(unsigned a, unsigned b)
{
    asm("addl %2,%0\n\t" "adcl $0,%0":"=r"(a)
            :    "0"(a), "r"(b));
    return a;
}

static inline unsigned do_csum(const unsigned char *buff, unsigned len)
{
    unsigned odd, count;
    unsigned long result = 0;

    if (unlikely(len == 0))
        return result;
    odd = 1 & (unsigned long)buff;
    if (unlikely(odd)) {
        result = *buff << 8;
        len--;
        buff++;
    }
    count = len >> 1;    /* nr of 16-bit words.. */
    if (count) {
        if (2 & (unsigned long)buff) {
            result += *(const unsigned short *)buff;
            count--;
            len -= 2;
            buff += 2;
        }
        count >>= 1;    /* nr of 32-bit words.. */
        if (count) {
            unsigned long zero;
            unsigned count64;
            if (4 & (unsigned long)buff) {
                result += *(const unsigned int *)buff;
                count--;
                len -= 4;
                buff += 4;
            }
            count >>= 1;    /* nr of 64-bit words.. */

            /* main loop using 64byte blocks */
            zero = 0;
            count64 = count >> 3;
            while (count64) {
                asm("addq 0*8(%[src]),%[res]\n\t" "adcq 1*8(%[src]),%[res]\n\t" "adcq 2*8(%[src]),%[res]\n\t" "adcq 3*8(%[src]),%[res]\n\t" "adcq 4*8(%[src]),%[res]\n\t" "adcq 5*8(%[src]),%[res]\n\t" "adcq 6*8(%[src]),%[res]\n\t" "adcq 7*8(%[src]),%[res]\n\t" "adcq %[zero],%[res]":[res]
                        "=r"(result)
                        :    [src] "r"(buff),[zero] "r"(zero), "[res]"(result));
                buff += 64;
                count64--;
            }

            /* last up to 7 8byte blocks */
            count %= 8;
            while (count) {
                asm("addq %1,%0\n\t" "adcq %2,%0\n":"=r"(result)
                        :    "m"(*(const unsigned long *)buff), "r"(zero), "0"(result));
                --count;
                buff += 8;
            }
            result = add32_with_carry(result >> 32, result & 0xffffffff);

            if (len & 4) {
                result += *(const unsigned int *)buff;
                buff += 4;
            }
        }
        if (len & 2) {
            result += *(const unsigned short *)buff;
            buff += 2;
        }
    }
    if (len & 1)
        result += *buff;
    result = add32_with_carry(result >> 32, result & 0xffffffff);
    if (unlikely(odd)) {
        result = from32to16(result);
        result = ((result >> 8) & 0xff) | ((result & 0xff) << 8);
    }
    return result;
}

static inline uint32_t csum_partial(const void *buff, int len, uint32_t sum)
{
    return (uint32_t) add32_with_carry(do_csum(buff, len), (uint32_t) sum);
}

static inline int build_tcp_packet(uint8_t *buf, const uint8_t *src_mac, const uint8_t *dst_mac,
        uint32_t src_ip, uint32_t dst_ip, uint16_t src_port, uint16_t dst_port,
        uint32_t seq, uint32_t ack_seq, int syn, int ack, int fin, int rst, uint16_t win,
        const void *packet_data, int packet_data_len, const void *tail, int tail_len)
{
    int data_len = 14 + sizeof(struct iphdr) + sizeof(struct tcphdr) + packet_data_len;
    struct iphdr *ih = (struct iphdr *)(buf + 14);
    struct tcphdr *th = (struct tcphdr *)(buf + 14 + sizeof(struct iphdr));

    memset(buf, 0, data_len);
    memcpy(buf + 6, src_mac, 6);
    memcpy(buf,     dst_mac, 6);
    memcpy(buf + 12, "\x08\x00", 2);
    ih->version = 4;
    ih->ihl = sizeof(struct iphdr) / 4;
    ih->tot_len = htons(sizeof(struct iphdr) + sizeof(struct tcphdr) + packet_data_len + tail_len);
    ih->frag_off = htons(0x4000);
    ih->ttl = 64;
    ih->protocol = IPPROTO_TCP;
    ih->saddr = src_ip;
    ih->daddr = dst_ip;
    th->source = src_port;
    th->dest = dst_port;
    th->doff = sizeof(struct tcphdr) / 4;
    th->ack = ack;
    th->fin = fin;
    th->rst = rst;
    th->seq = seq;
    th->syn = syn;
    th->ack_seq = ack_seq;
    th->window = htons(win);
    memcpy((char *)(th + 1), packet_data, packet_data_len);
    memcpy((char *)(th + 1) + packet_data_len, tail, tail_len);
    ih->check = ip_fast_csum((unsigned char *)ih, ih->ihl);
    th->check = csum_tcpudp_magic(ih->saddr, ih->daddr, sizeof(struct tcphdr) + packet_data_len, ih->protocol, csum_partial(th, sizeof(struct tcphdr) + packet_data_len, 0));
    return data_len + tail_len;
}

static inline uint16_t csum_ipv6_magic(const struct in6_addr *saddr, const struct in6_addr *daddr, __u32 len, __u8 proto, __wsum sum)
{
    uint64_t rest, sum64;
    rest = (uint64_t)htonl(len) + (uint64_t)htons(proto) + (uint64_t)sum;
    asm("   addq (%[saddr]),%[sum]\n"
            "   adcq 8(%[saddr]),%[sum]\n"
            "   adcq (%[daddr]),%[sum]\n"
            "   adcq 8(%[daddr]),%[sum]\n"
            "   adcq $0,%[sum]\n"
            : [sum] "=r" (sum64)
            : "[sum]" (rest), [saddr] "r" (saddr), [daddr] "r" (daddr));
    return csum_fold((uint32_t)add32_with_carry(sum64 & 0xffffffff, sum64>>32));
}

static inline int build_tcp_packet_v6(uint8_t *buf, const uint8_t *src_mac, const uint8_t *dst_mac,
        void *src_ip, void *dst_ip, uint16_t src_port, uint16_t dst_port,
        uint32_t seq, uint32_t ack_seq, int syn, int ack, int fin, int rst, unsigned win,
        const void *packet_data, int packet_data_len, const void *tail, int tail_len)
{
    int data_len = 14 + sizeof(struct ip6_hdr) + sizeof(struct tcphdr) + packet_data_len;
    struct ip6_hdr *ih = (struct ip6_hdr *)(buf + 14);
    struct tcphdr *th = (struct tcphdr *)(buf + 14 + sizeof(struct ip6_hdr));

    memset(buf, 0, data_len);
    memcpy(buf + 6, src_mac, 6);
    memcpy(buf,     dst_mac, 6);
    memcpy(buf + 12, "\x86\xdd", 2);
    ih->ip6_flow = htonl(0x60000000);
    ih->ip6_plen = htons(sizeof(struct tcphdr) + packet_data_len);
    ih->ip6_nxt = 6;
    ih->ip6_hlim = 62;
    memcpy(&ih->ip6_src, src_ip, 16);
    memcpy(&ih->ip6_dst, dst_ip, 16);
    th->source = src_port;
    th->dest = dst_port;
    th->doff = sizeof(struct tcphdr) / 4;
    th->ack = ack;
    th->fin = fin;
    th->rst = rst;
    th->seq = seq;
    th->syn = syn;
    th->ack_seq = ack_seq;
    th->window = htons((win + 255)/256);
    memcpy((char *)(th + 1), packet_data, packet_data_len);
    memcpy((char *)(th + 1) + packet_data_len, tail, tail_len);
    th->check = csum_ipv6_magic(src_ip, dst_ip, sizeof(struct tcphdr), IPPROTO_TCP, csum_partial(th, sizeof(struct tcphdr) + packet_data_len, 0));
    return data_len + tail_len;
}

#endif
